test

12.1 JMAPI

本章首先介绍一下JMAPI,其全称为 JRockit Management API,是一个轻量级的纯Java的API,提供了在进程内访问管理特性的功能。在JRockit早起的版本中就已经存在该API,但在R28.0.0版本中,该API已经被部分废弃,前途未卜。

JMAPI时JVM内部的API,可算作是早期版本的JRockit Management Console。事实上,即使在今天,若是连接到JRockit 1.4版本的JVM实例上,使用的仍然是名为 Rockit Management Protocol(BMP)的私有协议,而协议就是使用JMAPI来堆运行时信息做收集和修改操作的。

接下来介绍几个使用JMAPI的示例,若想编译这些示例,最好使用JRockit JDK。编译时无需做特殊配置,因为所需的类都在JRockit JDK的rt.jar包中。在编译示例的时候,也可以加上jmapi.jar包,该包中包含了所有接口声明。jmapi.jar包不是JDK的一部分,是由Oracle单独发行的。

使用JMAPI来完成一些小任务是非常简单的。com.bea.jvm.JVMFactory类可以得到实现了JVM接口的实例,通过该实例就可以访问到JVM的各个子系统了。

Figure 10-1

2008年,Oracle收购了BEA公司,而JMAPI是在此之前出现的,因此类的包名中会含有bea。Oracle在收购了BEA之后,将JMAPI用在其它Oracle产品和一些第三方产品上。由于JRockit R27及其之前的版本军支持JMAPI,所以为了不影响已有的产品,包名中的bea就被保留了下来。

在下面的示例中,会在控制台上打印出系统当前的CPU负载,共打印10次,每次间隔1秒钟。

import com.bea.jvm.JVMFactory;

public class JMAPITest {
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            System.out.println(
            String.format("CPU load is %3.2f%%",
            JVMFactory.getJVM().getMachine().getCPULoad() * 100.0));
            Thread.sleep(1000);
        }
    }
}

访问JMAPI需要对相关权限进行配置,而无法细粒度设置权限,只能"全部允许"或"全部禁止"。若是启用了安全管理器(security manager),则需要为com.bea.jvm.ManagementPermission赋予createInstance权限。如下所示:

grant {
    permission com.bea.jvm.ManagementPermission "createInstance";
};

更多有关权限与安全控制方面的内容,请参见http://java.sun.com/j2se/1.5.0/docs/guide/security/permissions.html

12.1.1 JMAPI示例

JMAPI可用于收集有关操作环境的各种信息。在下面的示例中,演示了如何通过JMAPI来获取有关网络接口方面的信息:

for (NIC nic : JVMFactory.getJVM().getMachine().getNICs()) {
    System.out.println(
            nic.getDescription() + " MAC:" +
            nic.getMAC() + " MTU:" + nic.getMTU());
}

还可以使用JMAPI修改运行时参数。在下面的示例中,会将JRockit进程中的线程都绑定到一个CPU上:

private static void bindToFirstCPU(JVM jvm) {
    Collection<CPU> cpus = jvm.getProcessAffinity();
    CPU cpu = cpus.iterator().next();
    Collection<CPU> oneCpu = new LinkedList<CPU>();
    oneCpu.add(cpu);
    jvm.suggestProcessAffinity(oneCpu);
}

上面示例所实现的功能与使用命令参数-XX:BindToCPUs相同,可以控制CPU亲和性。参见第5章的相关内容。

其他例如暂停时间、堆大小和年轻代大小等内容,也可以进行调整:

MemorySystem ms = JVMFactory.getJVM().getMemorySystem();
ms.suggestHeapSize(1024*1024*1024);
ms.getGarbageCollector().setPauseTimeTarget(30);
ms.getGarbageCollector().setNurserySize(256*1024*1024);

某些JMAPI中的特性需要用户做一些特别设置。有些奇怪的念头,例如在JVM内存不足时,不要抛出OutOfMemoryError错误,而是强制终止JRockit进程的运行。

ms.setExitOnOutOfMemory(true);

此外,还可以使用JMAPI做一些简单的方法分析。在下面的示例中,启用了对java.io.StringWriter#append(CharSequence)方法分析。在每次调用该方法时,都会打印出方法的平均执行时间。

import java.io.StringWriter;
import java.lang.reflect.Method;

import com.bea.jvm.JVMFactory;
import com.bea.jvm.MethodProfileEntry;
import com.bea.jvm.ProfilingSystem;

public class MethodProfilerExample {
    public static void main(String[] args) throws Exception {
        String longString = generateLongString();
        ProfilingSystem profiler = JVMFactory.getJVM().getProfilingSystem();
        Method appendMethod = StringWriter.class.getMethod("append", CharSequence.class);
        MethodProfileEntry mpe = profiler.newMethodProfileEntry(appendMethod);
        mpe.setInvocationCountEnabled(true);
        mpe.setTimingEnabled(true);

        String total = doAppends(10000, longString);
        long invocationCount = mpe.getInvocations();
        long invocationTime = mpe.getTiming();
        System.out.println("Did " + invocationCount + " invocations");
        System.out.println("Average invocation time was " + (invocationTime * 1000.0d) / invocationCount + " microseconds");
        System.out.println("Total string length " + total.length());
    }

    private static String doAppends(int count, String longString) {
        StringWriter writer = new StringWriter();
        for (int i = 0; i < count; i++) {
            writer.append(longString);
        }
        return writer.toString();
    }

    private static String generateLongString() {
        StringWriter sw = new StringWriter(1000);
        for (int i = 0; i < 1000; i++) {
            // Build a string containing the characters
            // A to Z repeatedly.
            sw.append((char) (i % 26 + 65));
        }
        return sw.toString();
    }
}

上面的示例比较简单。正常情况下,分析功能本来就是启用的,因此MethodProfileEntry中的计数器和计时信息在执行分析之前就已经保存下来了,在分析结束后也可以正常提取出来。

回忆一下第7章第11章的内容,其中介绍的诊断命令都可以通过JMAPI实现,而且还可以直接访问DiagnosticCommand子系统。在下面的示例中,会模拟print_object_summary命令的实现,在控制台中打印出对象汇总信息的直方图:

import com.bea.jvm.DiagnosticCommand;
import com.bea.jvm.JVMFactory;

public class ObjectSummary {
    public static void main(String[] args) throws InterruptedException {
        DiagnosticCommand dc = JVMFactory.getJVM().getDiagnosticCommand();
        String output = dc.execute("print_object_summary");
        System.out.println(output);
    }
}

最后,JMAPI可以实现类的预处理和重定义。下面的示例展示了如何在载入类的时候重定义类的字节码:

ClassLibrary cl = JVMFactory.getJVM().getClassLibrary();
cl.setClassPreProcessor(new ClassPreProcessor() {
    @Override
    public byte[] preProcess(ClassLoader cl, String className, byte[] arg) {
        System.out.println("Pre-processing class " + className);
        return transformByteCode(arg);
    }
});

任意时间,只能有一个活动的预处理器。通过redefineClass方法,还可以重定义已经载入过的类。

JMAPI的用处还有很多,篇幅所限,这里就不再介绍那些废弃的货已经不再支持的特性。更多有关JMAPI内容,请参见Oracle官方文档。